package de.htw.noise;

import java.util.Random;

public class PerlinNoise {
	
	protected int octaves;
	protected short seed; // = Long.MAX_VALUE >> 1;
	protected double persistence;
	
	private long prime0, prime1, prime2, prime3, prime4, prime5;
	
	protected Interpolation2D interpolation;
	
	Random random;
	
	public PerlinNoise(final short seed, final double persistence, final int octaves) {
		this.interpolation = new Interpolation2D();
		this.persistence  = persistence;	
		this.octaves = octaves;
		this.seed = seed;
		// create some primes to generate some noise		
		loadPrimes(seed);
	}

	private void loadPrimes(final short seed) {
		prime0 = Primes.P[0];
		prime1 = Primes.P[1]; 
		prime2 = Primes.P[2]; 
		prime3 = Primes.P[Primes.P.length / 2]; 
		prime4 = Primes.P[Primes.P.length - 2]; 
		prime5 = Primes.P[Primes.P.length - 1]; 
	}
	
	public static void main(String[] args) {
		new PerlinNoise((short) 512, 0.2, 2);
	}

	public void update(short seed, double persistence, int octaves) {
		this.seed = seed;
		this.persistence = persistence;
		this.octaves = octaves;
		loadPrimes(seed);
	}
	
	/**
	 * creates pseudo random numbers
	 * 
	 * @param x
	 * @param y
	 * @return a double precision value between [-1, 1]
	 */
	protected double noise2D(double x, double y) {
		int n = (int) (seed + x + y * 57);
		n = (n << 13) ^ n;															 
		return (1.0 - ((n * (n * n * prime0 + prime1) + prime5) & 0x7fffffff)/ (double)prime5);
	}
	
	/**
	 * returns some smoothed noise 
	 * 
	 * @param x
	 * @param y
	 * @return smoothed noise value
	 */
	protected double smoothedNoise2D(double x, double y) {
		double co = (noise2D(x - 1, y - 1) + noise2D(x + 1, y-1) + noise2D(x, y + 1) + noise2D(x + 1, y + 1)) / 8.;
		double si = (noise2D(x - 1, y) + noise2D(x + 1, y) + noise2D(x, y + 1) + noise2D(x, y - 1)) / 4.;
		double ce = noise2D(x,y);
		return (co + si + ce);
	}
	
	/**
	 * Returns some interpolated noise
	 * @param x
	 * @param y
	 * @return
	 */
	protected double interpolatedNoise2D(double x, double y) {
		int xx = (int)x;
		int yy = (int)y;
		double f0 = x - xx;
		double f1 = y - yy;
		
		double v0 = smoothedNoise2D(xx, yy);
		double v1 = smoothedNoise2D(xx + 1, yy);
		double v2 = smoothedNoise2D(xx, yy + 1);
		double v3 = smoothedNoise2D(xx + 1, yy+1);
		
		double a = interpolation.cosine(f0, v0, v1);
		double b = interpolation.cosine(f0, v2, v3);
		
		return interpolation.cosine(f1, a, b);
	}
	
	/**
	 * Returns perlin noise, that is smoothed noise with a number of octaves
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public double perlinNoise2D(double x, double y) {
		double result = 0.0;
		double a = 0;
		double f = 0;
		int i = 0;
		while(i < octaves)
		{
			f = pow2( i );
			a = Math.pow(persistence,i);
			result += interpolatedNoise2D(x * f, y * f) * a;
			++i;
		}
		return result;
	}
	
	protected int pow(int base, int exp) {
		if(base == 2) return pow2(exp);
		if(exp == 0) return 1;
		if((exp & 1) == 0) return base * pow(base * base, exp >> 1);
		return pow(base * base, exp - 1);
	}
	
	protected int pow2(int exp) {
		if( exp > 31) return -1;
		return 2 << exp;
	}

}
